//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
//system header files
#include <ddk.h>

#include "xymdm.h"

#define U_COM 0

#if U_COM
#define CONCOM(x) x
#else
#define CONCOM(x)
#endif

//EXTERN_C void hexouts(UInt8* buf, UInt32 buflen);

CXYmodem::CXYmodem()
{
    m_IsXmodem1k = TRUE;	/* 1k blocks supported */
    m_IsYmodem = TRUE;
    m_IsUseCrc = TRUE;	/* receiver wants crc */

    m_State = X_START;
    m_Protocol = YMDM;
    m_XmTimeout = INITTO;	/* timeout, seconds */

    m_ErrorCount = m_ErrorCount2 = m_IgnoreCount = 0;
    m_EotCount = 0;	/* count EOT's, reject first one */
    m_InCount = 0;	  /* characters received this packet */
    m_PktLen = MAXPACKET;	/* length of this packet data */
    m_PktHdrLen = (PKGHDR + CRCLEN);	/* id, cmpl, checksum or crc */
    m_PacketId = 255;	/* id of last received packet */
    m_PacketCount = 0;	/* # packets received */
}


CXYmodem::~CXYmodem()
{

}




//return : -1 error ; other : packet status
int CXYmodem::RECV(char* PacketBuf, int acstatus)
{
    int err = 0;
    int pkstatus = PK_NONE;

    if (X_START == m_State) {
        return PK_CANCEL;
    }

    if (acstatus) {
        switch(acstatus) {
            case AC_NONE:
                break;

            case AC_REJECT:
                RECV_REJECT();
                break;

            case AC_ACCEPT:
                RECV_ACCEPT();
                break;

            case AC_YMD1STPK:
                m_PacketCount = 1;
        	    RECV_ACCEPT();
        	    if (m_Protocol != YMDMG) {
        	        Flush('C');
        	    }
        	    else {
        	        Flush('G');
        	    }
                break;

            case AC_CANCEL:
                Cancel();
                return PK_CANCEL;

            default:
                CONCOM(cprintf("NO ACTION STATUS!\n"));
                break;
        }
    }

    err = RECV_RECV(PacketBuf);
    if (XmFullBlk == err) {
        CONCOM(cprintf("Receive a full packet!\n"));
        err = RECV_PACKET(PacketBuf, &pkstatus);
    	if ( (0 == err) && (PK_VALIDDATA == pkstatus) ) {
    	    CONCOM(cprintf("VALID DATA PACKET!\n"));
        	/* is this the first packet? */
        	if( m_PacketCount == 0 ) {
        	    if( m_IsYmodem ) {
        	        CONCOM(cprintf("first packet file info!\n"));
        	        pkstatus = PK_FILEINFO;
        	        return pkstatus;
        	    }
        	}
        	++m_PacketCount;
        	return pkstatus;
        }
        else if (XmErrSequence == err) {
            return PK_CANCEL;
        }
    }
    else {
        CONCOM(cprintf("Receive packet error code : %08x!\n", err));
        return PK_CANCEL;
    }
    return pkstatus;
}

int CXYmodem::RECV_RECV(char* PacketBuf)
{
    int err, rv = 0;
    char *ptr = PacketBuf;
    char c;

    do {
        err = XmErrNone;
        rv = RecvStr(ptr, m_PktLen, m_XmTimeout);
        CONCOM(cprintf("RECV STRING LENGTH %d\n", rv));
        if (rv) {
            if ( (X_INIT == m_State) || (X_WAIT == m_State) ) {
                c = ptr[0];
                switch( c ) {
        	        case SOH :
        	        case STX :
                		m_DataLen = (c == STX) ? MAXPACKET : MINPACKET ;
                	    m_PktLen = m_DataLen + m_PktHdrLen;
                		m_InCount = 0;
                		m_State = X_PACKET;
                		m_XmTimeout = PKTTO;
                		CONCOM(cprintf("X_INIT STX/SOH %d BLK LEN %d\n", m_DataLen, m_PktLen));
                		break;

    	            case EOT :
    	                CONCOM(cprintf("EOT\n"));
    	                /* Remote should send twice to EOT declare Transfer finished */
                		if( ++m_EotCount > 1 ) {
                		    Flush(ACK);
                		    if( m_IsYmodem ) {
                		        /* restart protocol */
                		        RECV_INIT(m_Protocol);
                		        //return XmNewFile;
                		    }
                		    else {
                		        return XmDone;
                		    }
                		}
                		else {
                		    RECV_REJECT();	/* make xmitter try again */
                        }
                        break;

                	case CAN :
                	    return XmErrCancel;

    	            default :		/* ignore all others */
    	                CONCOM(cprintf("m_IgnoreCount:"));
    		            Cancel();
    		            return XmErrIgnore;
    	        }/*end switch(c)*/
            }
            err = XmErrNone;

            if (X_PACKET == m_State) {
                ptr += rv;
                m_InCount += rv;
                if (m_PktLen == m_InCount) {
                    err = XmFullBlk;
                }
            }
        }
        else {
            //TO DO: TIME OUT
            CONCOM(cprintf("RECV TIMEOUT:"));
        	switch( m_State ) {
        	    case X_INIT :
        	        CONCOM(cprintf("<X_INIT>:"));
                	if( ++m_ErrorCount > MAXERROR ) {
                	    CONCOM(cprintf("MAXERROR\n"));
                	    err = XmErrInitTo;
                    }
                    else {
                        CONCOM(cprintf("RESTART\n"));
        	            RECV_START(m_Protocol);
                    }
        	        break;

        	    case X_WAIT :			/* timeout while waiting */
        	        CONCOM(cprintf("<X_WAIT>\n"));
        	    case X_PACKET :			/* timeout in mid packet */
        	        CONCOM(cprintf("<X_PACKET>\n"));
                	if( ++m_ErrorCount > MAXERROR ) {
                	    Cancel();
                	    err = XmErrRcvTo;
                	}
                	else {
        	            if (YMDMG != m_Protocol) {
        	                RECV_REJECT();
                            ptr = PacketBuf;
                            m_InCount = 0;
        	            }
        	            else {
        	                Flush('G');
                            err = XmErrRcvTo;
                        }
                	}
                    break;

        	    default:
        	        CONCOM(cprintf("<default>\n"));
        	        Cancel();
        	        err = XmErrInt;
        	}
        }
    }while(XmErrNone == err);

    return err;
}

/* send startup character */
int CXYmodem::RECV_START(int protocol)
{
	char c;
	switch(protocol) {
	    case XMDM :
	        c = NAK;
	        m_IsUseCrc = FALSE;
	        m_XmTimeout = INITTO;
	        break;

	    case XMDMCRC :
	        c = 'C';
	        m_IsUseCrc = TRUE;
            m_XmTimeout = INITTO2;
	        break;

	    case YMDM :
	        c = 'C';
	        m_XmTimeout = INITTO;
	        break;

        case YMDMG :
	        c = 'G';
	        m_XmTimeout = INITTO;
	        break;

	    default:
	        m_Protocol = XMDM;
	        c = NAK;
	        m_IsUseCrc = FALSE;
	        m_XmTimeout = INITTO;
	}

	return Flush(c);
}


int CXYmodem::RECV_PACKET(char* Packet, int* pkstatus)
{
//    hexouts((UInt8*)Packet, m_PktLen);
	int	id  = (int)Packet[1];
	int	idc = (int)Packet[2];
	int	csum = 0;

	if ( idc != (255 - id) ) {
	    *pkstatus = PK_REJECT;
	    CONCOM(cprintf("RECV_REJECT idc err\n"));
	    return RECV_REJECT();
    }
	if ( id == m_PacketId ) {		/* duplicate */
	    *pkstatus = PK_DUPLICATE;
	    CONCOM(cprintf("RECV_ACCEPT duplicate\n"));
	    return RECV_ACCEPT();
    }

	if( id != ((m_PacketId + 1) % 256) ) {	/* out of sequence */
	    *pkstatus = PK_CANCEL;
	    Cancel();
	    CONCOM(cprintf("RECV_CANCEL out of sequence\n"));
	    return XmErrSequence;
	}


	if (m_IsUseCrc) {
	    int crc0 = ((UInt8)Packet[m_DataLen + PKGHDR] << 8) | ((UInt8)Packet[m_DataLen + PKGHDR + 1]);
	    int crc1 = CalCrc(Packet + PKGHDR, m_DataLen);
	    if( crc0 != crc1 ) {
	        *pkstatus = PK_REJECT;
	        CONCOM(cprintf("RECV_REJECT CRC err\n"));
	        return RECV_REJECT();
	    }
	}
	else {
	    /* compute checksum */
	    csum = CalcChecksum(Packet + PKGHDR, m_DataLen);
	    if( csum != (UInt8)Packet[PKGHDR + m_DataLen] ) {
	        *pkstatus = PK_REJECT;
	        CONCOM(cprintf("RECV_REJECT CheckSum err\n"));
	        return RECV_REJECT();
	    }
	}
	/* it's a good packet */
	m_PacketId = (m_PacketId + 1) % 256;
    *pkstatus = PK_VALIDDATA;
	return 0;
}


int CXYmodem::RECV_REJECT()
{
	m_State = X_WAIT;
	m_XmTimeout = INITTO ;
	return Flush(NAK) ;
}

int CXYmodem::RECV_ACCEPT()
{
    m_State = X_WAIT;
    m_XmTimeout = INITTO;
    if (m_Protocol != YMDMG)
        return Flush(ACK);
    return 0;
}



int CXYmodem::RECV_INIT(int protocol)
{
	m_State = X_START;
	int err;
    m_Protocol = protocol;
	if ( (YMDM == m_Protocol) || (YMDMG == m_Protocol) ) {
	    m_IsYmodem = TRUE;
	}
    else {
        m_IsYmodem = FALSE;
        if (XMDM == m_Protocol) {
            m_IsUseCrc = FALSE;
        }
    }

	m_EotCount = m_ErrorCount = m_ErrorCount2 = 0;

    err = RECV_START(m_Protocol);
	if( err )
	  return err;

	m_State = X_INIT;
	m_PacketId = m_IsYmodem ? 255 : 0;
	m_PktHdrLen = (XMDM == m_Protocol) ? PKGHDR + CSMLEN : PKGHDR + CRCLEN;
	m_PacketCount = 0;
    CONCOM(cprintf("RECV INTI .......... \n"));
	return XmErrNone ;
}


int CXYmodem::XMIT(char *packet, int pkstatus, int xmitl)
{
    int rv, ac_status = 0;

    if ( (PK_FILEINFO == pkstatus) || (PK_EOT == pkstatus) ) {
        m_PacketId = 0;
        XMIT_BUILD(packet, xmitl);
//        hexouts((UInt8*)packet, m_PktLen);

        ac_status = XMIT_LISTEN();
        if (AC_CANCEL == ac_status) {
            goto XMIT_ERR;
        }
        if (AC_DATA == ac_status) {
            rv = XMIT_XMIT(packet);
            if(AC_CANCEL == rv) {
                CONCOM(cputs("XMIT PK_FILEINFO A\n"));
                return rv;
            }

            ac_status = XMIT_LISTEN();
            switch(ac_status) {
                case AC_ACCEPT:
                    if (PK_EOT == pkstatus) {
                        ac_status = AC_FILEINFO;
                        break;
                    }
                    ac_status = XMIT_LISTEN();
                    if (AC_CANCEL == ac_status) {
                        goto XMIT_ERR;
                    }
                    if (AC_DATA == ac_status) {
                        ac_status = AC_FILEINFO;
                        CONCOM(cputs("XMIT PK_FILEINFO B\n"));
                    }

                    if (AC_DATA != ac_status) {
                        break;
                    }

                case AC_DATA:
                    ac_status = AC_FILEINFO;//batch transfer end
                    CONCOM(cputs("XMIT PK_FILEINFO C\n"));

                case AC_CANCEL:
                    goto XMIT_ERR;
            }
//            if (AC_ACCEPT == ac_status) {
//                if (xmitl) {//a new file transfer
//                    ac_status = XMIT_LISTEN();
//                    if (AC_DATA == ac_status) {
//                        ac_status = AC_FILEINFO;
//                        CONCOM(cputs("XMIT PK_FILEINFO B\n"));
//                    }
//                }
//                else {
//                    ac_status = AC_FILEINFO;//batch transfer end
//                    CONCOM(cputs("XMIT PK_FILEINFO C\n"));
//                }
//            }
//            else if (AC_DATA == ac_status) {
//                ac_status = AC_FILEINFO;
//                CONCOM(cputs("XMIT PK_FILEINFO D\n"));
//            }
        }
        if (AC_FILEINFO != ac_status) {
            CONCOM(cputs("XMIT PK_FILEINFO E\n"));
            ac_status = AC_TIMEOUT;
        }
    }
    else if (PK_VALIDDATA == pkstatus){//DATA TRANSFER

        XMIT_BUILD(packet, xmitl);
//        hexouts((UInt8*)packet, m_PktLen);

        do {
            rv = XMIT_XMIT(packet);
            if(AC_CANCEL == rv) {
                CONCOM(cputs("XMIT PK_VALIDDATA A\n"));
                return rv;
            }
            ac_status = XMIT_LISTEN();
        }while(AC_REJECT == ac_status);
    }
    if (AC_CANCEL == ac_status) {
        goto XMIT_ERR;
    }
    if ( X_EOT == m_State ) {
        do {
            SendChar(EOT);
            ac_status = XMIT_LISTEN();
            if (AC_CANCEL == ac_status) {
                goto XMIT_ERR;
            }
        }while(AC_REJECT == ac_status);
        ac_status = AC_EOT;
        m_State = X_START;
    }
XMIT_ERR:
    if (AC_CANCEL == ac_status) {
        CONCOM(cputs("XMIT AC_CANCEL A\n"));
    }
    return ac_status;
}

//return val : remote response
int CXYmodem::XMIT_LISTEN()
{
    int re_acs = AC_TIMEOUT, rv, canerr = 0;
    char res_c;

xxx_retry:
    rv = RecvStr(&res_c, 1, m_XmTimeout);
    if (0 == rv) {
        return AC_TIMEOUT;
    }
    CONCOM(cprintf("RECV REMOTE CHAR [%c][%02x]\n", res_c, res_c));
    switch( res_c ) {
        case ACK:
            re_acs = AC_ACCEPT;
            break;

        case NAK:
            re_acs = AC_REJECT;
            break;

        case 'C':
        case 'G':
            re_acs = AC_DATA;
            break;

        case CAN:
            canerr++;
//            cputs("XXX RECEIVED!\n");
            if (canerr < 2) {
                goto xxx_retry;
            }
            re_acs = AC_CANCEL;
            break;

        default:
            break;
    }
	return re_acs;
}

int CXYmodem::XMIT_BUILD(char *packet, int xmitl)
{
	int	i;
	char *p = packet;
    CONCOM(cprintf("XMIT_BUILD DATA LENGTH %0d\n",m_DataLen));

    if (xmitl < m_DataLen) {
        p = packet + PKGHDR + xmitl;// the ptr begin to fill dummy data
        for(i = 0; i < (m_DataLen - xmitl);) {
	        p[i++] = 0x1A;
	    }
	    CONCOM(cprintf("TRANSFER FILE REACH TO END!\n"));
	    m_State = X_EOT;
//	    return sendChr(EOT);
    }

	packet[0] = (m_DataLen == MAXPACKET) ? STX : SOH;
	packet[1] = (char)m_PacketId;
	packet[2] = (char)(255 - m_PacketId);
	m_PacketId = (m_PacketId + 1) % 256;
//	++m_PacketId;

	if( m_IsUseCrc ) {
	    i = CalCrc(packet + PKGHDR, m_DataLen);
	    packet[m_DataLen + PKGHDR] = (char)(i >> 8);
	    packet[m_DataLen + PKGHDR + 1] = (char)(i & 0xff);
	    CONCOM(cprintf("TRANSFER CRC %02x %02x\n", packet[m_DataLen + PKGHDR], packet[m_DataLen + PKGHDR + 1]));
	}
	else {
	    packet[m_DataLen + PKGHDR] = (char)CalcChecksum(packet + PKGHDR, m_DataLen);
    }
	//return resendPacket();
	return 0;
}


int CXYmodem::XMIT_XMIT(char* packet)
{
    CONCOM(cputs("XMIT START.......!\n"));
    if(SendStr(packet, m_PktLen)) {
        Cancel();//can't send.....link crashed
        return AC_CANCEL;
    }
    CONCOM(cputs("XMIT OVER .......!\n"));
    return 0;
}

int CXYmodem::XMIT_INIT(int protocol)
{
	m_Protocol = protocol;

	m_PacketId = 1 ;
	m_PacketCount = 0 ;
	if ( XMDM != m_Protocol ) {
	    m_IsUseCrc = TRUE;
	    m_PktHdrLen = PKGHDR + CRCLEN;
	    CONCOM(cprintf("CRC TRUE HDRLEN %d\n", m_PktHdrLen));
    }
	switch(m_Protocol) {
	    case XMDM :
	        m_XmTimeout = INITTO;
	        m_IsUseCrc = FALSE;
	        m_PktHdrLen = PKGHDR + CSMLEN;
	        break;

	    case YMDM :
        case YMDMG :
	        m_XmTimeout = INITTO;
	        break;

	    case XMDMCRC :
            m_XmTimeout = INITTO2;
	        break;

	    default:
	        m_XmTimeout = INITTO;
	}

	return XmErrNone ;
}


ECode CXYmodem::Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    int recv_pks;
    if (NULL == pBuffer) {
        return E_INVALID_ARGUMENT;
    }
    P_XYM_PKG pRxPKG = (P_XYM_PKG(pBuffer->GetPayload()));
    char *pRxBuf = pRxPKG->PacketData;
    int ac_st = pRxPKG->ActionStatus;

    recv_pks = RECV(pRxBuf, ac_st);
    CONCOM(cprintf("RECV PKG STATUS %08x\n", recv_pks));

    pRxPKG->PacketStatus = recv_pks;
    pRxPKG->PacketDataLen = m_DataLen;
    pBuffer->SetUsed(sizeof(XYM_PKG));

    return NOERROR;
}


ECode CXYmodem::Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf &buffer,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    P_XYM_PKG pTxPKG = (P_XYM_PKG(buffer.GetPayload()));
    char *pTxBuf = pTxPKG->PacketData;
    int ac_st, xmit_pks, xmit_dl;
    xmit_pks = pTxPKG->PacketStatus;
    xmit_dl  = pTxPKG->PacketDataLen;
    m_DataLen = (xmit_dl > MINPACKET) ? MAXPACKET : MINPACKET;
    m_PktLen = m_DataLen + m_PktHdrLen;

    CONCOM(cprintf("XMIT PKG LEN %d\n", m_PktLen));
    ac_st = XMIT(pTxBuf, xmit_pks, xmit_dl);
    CONCOM(cprintf("XMIT PKG ACTION STATUS %08x\n", ac_st));

    pTxPKG->ActionStatus = ac_st;
    return NOERROR;
}

ECode CXYmodem::Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf &inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec = NOERROR;
    char *InData = (char*)inBuffer.GetPayload();
    char *OutData = NULL;
    int err, dwCnt, dwRead;//For Read To Timeout Cnt

    switch(nControlCode) {
        case 0x0A:
            err = RECV_INIT(YMDM);
            if ( err ) {
                ec = E_FAIL;
            }
            break;

        case 0x0B:
            err = RECV_INIT(YMDMG);
            if ( err ) {
                ec = E_FAIL;
            }
            break;

        case 0x1A:
            err = XMIT_INIT(YMDM);
            if ( err ) {
                ec = E_FAIL;
            }
            break;

        case 0x1B:
            err = XMIT_INIT(YMDMG);
            if ( err ) {
                ec = E_FAIL;
            }
            break;


        case AC_REJECT:
            err = RECV_REJECT();
            if ( err ) {
                ec = E_FAIL;
            }
            break;

        case AC_ACCEPT:
            err = RECV_ACCEPT();
            if ( err ) {
                ec = E_FAIL;
            }
            break;

        case AC_CANCEL:
            err = Cancel();
            if ( err ) {
                ec = E_FAIL;
            }
            break;

        case AC_YMD1STPK:
            m_PacketCount = 1;
            RECV_ACCEPT();
            if (m_Protocol != YMDMG) {
                err =Flush('C');
            }
            else {
                err = Flush('G');
            }
            if ( err ) {
                ec = E_FAIL;
            }
            break;

        case AC_READ:
            break;

        case AC_READTO:
            assert(pOutBuffer != NULL);
            OutData = (char *)pOutBuffer->GetPayload();
            dwCnt = pOutBuffer->GetCapacity();
            dwRead = 0;
            while( dwCnt > 0 ) {

                dwRead = RecvStr(OutData, dwCnt, 3);
                if (0 == dwRead) {
                    break;
                }
//                cprintf("%03d RecvStr %s\n", dwCnt, OutData);
                dwCnt -= dwRead;
                OutData+=dwRead;
            }
            pOutBuffer->SetUsed(pOutBuffer->GetCapacity() - dwCnt);
            break;

        case AC_WRITE:
            SendStr(InData, inBuffer.GetUsed());
            break;

        case AC_SHANKE:
            Flush('Q');
            Flush('T');
            Flush('M');
            break;

        default:
            break;
    }
    return ec;
}

void CXYmodem::Dispose()
{}

CXYmodem g_XYmdm;
EXTERN IDeviceDriver * CDECL CreateXYmdm(uint_t uDeviceNo, void *pvParameter)
{
    return (&g_XYmdm);
}


